import * as d3 from 'd3'
export class ChordChart {
    divId = ''
    data = []
    chart = {}
    basicRadius = 0
    firstDraw = true
    innerRadius = '80%'
    outerRadius = '90%'
    labelRadius = '92%'
    color = d3.scaleOrdinal(d3.schemeCategory10)

    constructor (divId) {
      this.divId = divId
    }

    draw () {
      this.initChart()
      this.initLayout()
      this.drawChord()
    }

    update () {
      this.drawChord()
    }

    initChart () {
      this.chart.padding = {
        left: 10,
        right: 10,
        top: 10,
        bottom: 10
      }
      this.chart.divHeight = document.getElementById(this.divId).offsetHeight -
          this.chart.padding.top -
          this.chart.padding.bottom
      this.chart.divWidth = document.getElementById(this.divId).offsetWidth -
          this.chart.padding.left -
          this.chart.padding.right

      this.basicRadius = Math.min(this.chart.divHeight, this.chart.divWidth) / 2

      this.chart.svg = d3
        .select('#' + this.divId)
        .append('svg')
        .attr('width', this.chart.divWidth +
            this.chart.padding.left +
            this.chart.padding.right
        )
        .attr('height', this.chart.divHeight +
            this.chart.padding.top +
            this.chart.padding.bottom
        )
        .attr('class', 'svg_chart')
    }

    initLayout () {
      this.chart.arc = d3.arc()
        .innerRadius(this.basicRadius * parseFloat(this.innerRadius.split('%')).toFixed(1) / 100)
        .outerRadius(this.basicRadius * parseFloat(this.outerRadius.split('%')).toFixed(1) / 100)

      this.chart.labelArc = d3.arc()
        .innerRadius(this.basicRadius * parseFloat(this.labelRadius.split('%')).toFixed(1) / 100)
        .outerRadius(this.basicRadius * parseFloat(this.labelRadius.split('%')).toFixed(1) / 100)

      this.chart.chord = d3.chord()
        .padAngle(0.03)
        .sortSubgroups(d3.ascending)

      this.chart.ribbon = d3.ribbon()
        .radius(this.basicRadius * parseFloat(this.innerRadius.split('%')).toFixed(1) / 100)

      this.chart.gChord = this.chart.svg.append('g')
        .attr('class', 'chord')
        .attr('transform', 'translate(' + this.chart.divWidth / 2 + ',' + this.chart.divHeight / 2 + ')')
    }

    drawChord () {
      this.drawOuter()
      this.drawInner()
    }

    drawOuter () {
      let arc = this.chart.arc
      let labelArc = this.chart.labelArc
      let nodes = this.chart.chord(this.data.relationship).groups

      let gOuter = this.chart.gChord.selectAll('.outer')
        .data(nodes)

      gOuter.selectAll('path.mainPath')
        .transition()
        .duration(1000)
        .attrTween('d', arcTween)

      gOuter.select('path.labelPath')
        .transition()
        .duration(1000)
        .attrTween('d', hiddenTween)

      let outerEnter = gOuter.enter()
        .append('g')
        .attr('class', 'outer')

      outerEnter.append('path')
        .attr('d', d => this.chart.arc({
          startAngle: d.startAngle,
          endAngle: d.startAngle
        }))
        .attr('class', 'mainPath')
        .style('fill', (_, i) => this.color(i))
        .style('stroke', 'black')
        .style('stroke-width', 1)
        .each(function (d) {
          this._current = {
            startAngle: d.startAngle,
            endAngle: d.startAngle
          }
        })

      outerEnter.append('path')
        .attr('d', d => this.chart.labelArc({
          startAngle: d.startAngle,
          endAngle: d.startAngle
        }))
        .attr('id', (_, i) => 'hidden-' + i)
        .attr('class', 'labelPath')
        .each(function (d) {
          this._current = {
            startAngle: d.startAngle,
            endAngle: d.startAngle
          }
        })

      let text = outerEnter.append('text')

      text.append('textPath')
        .attr('startOffset', '20%')
        .attr('xlink:href', (_, i) => {
          return '#hidden-' + i
        })
        .style('font-size', 16)
        .text((_, i) => this.data.name[i])

      let outerUpdate = outerEnter.merge(gOuter)

      outerUpdate.select('path.mainPath')
        .transition()
        .duration(1000)
        .delay(d => this.firstDraw === true ? 0 : 1000)
        .attrTween('d', arcTween)

      outerUpdate.select('path.labelPath')
        .transition()
        .duration(1000)
        .delay(d => this.firstDraw === true ? 0 : 1000)
        .attrTween('d', hiddenTween)

      let outerExit = gOuter.exit()
        .transition()
        .duration(1000)
        .remove()

      outerExit.select('path')
        .attrTween('d', exitTween)

      if (this.firstDraw === true) {
        this.firstDraw = false
      }

      function arcTween (a) {
        let i = d3.interpolate(this._current, a)
        this._current = i(0)
        return function (t) {
          return arc(i(t))
        }
      }

      function hiddenTween (a) {
        let i = d3.interpolate(this._current, a)
        this._current = i(0)
        return function (t) {
          return labelArc(i(t))
        }
      }

      function exitTween (d) {
        var end = Object.assign({}, this._current, { startAngle: this._current.endAngle })
        var i = d3.interpolate(d, end)
        return function (t) {
          return arc(i(t))
        }
      }
    }

    drawInner () {
      let ribbon = this.chart.ribbon
      let links = this.chart.chord(this.data.relationship)

      let gInner = this.chart.gChord.selectAll('.inner')
        .data(links, d => d.source.index + '->' + d.target.index)

      gInner.selectAll('path')
        .transition()
        .duration(1000)
        .attrTween('d', chordTween)

      let innerEnter = gInner.enter()
        .append('g')
        .attr('class', 'inner')

      innerEnter.append('path')
        .style('stroke', 'black')
        .style('stroke-width', 1)
        .style('fill', (_, i) => this.color(i))
        .attr('d', d => {
          this.chart.ribbon({
            source: d.source,
            target: d.source
          })
        })
        .each(function (d) {
          this._current = {
            source: d.source,
            target: d.source
          }
        })

      let innerUpdate = innerEnter.merge(gInner)

      innerUpdate.select('path')
        .transition()
        .duration(1000)
        .delay(1000)
        .attrTween('d', chordTween)

      let innerExit = gInner.exit()
        .transition()
        .duration(750)
        .remove()

      innerExit.select('path')
        .attrTween('d', exitTween)

      function chordTween (a) {
        let i = d3.interpolate(this._current, a)
        this._current = i(0)
        return function (t) {
          return ribbon(i(t))
        }
      }

      function exitTween (d) {
        var end = Object.assign({}, this._current, { target: this._current.source })
        var i = d3.interpolate(d, end)
        return function (t) {
          return ribbon(i(t))
        }
      }
    }
}
